
#ifndef __RSP_S_WORLD__HEADER_FILE__
#define __RSP_S_WORLD__HEADER_FILE__

#include "coid/comm/binstream/netstreamudp.h"
#include "coid/comm/rnd.h"

#include "controller.h"
#include "s_connector.h"
//#include "s_object.h"
#include "viewcont.h"
//#include "classdef.h"

using namespace coid;


////////////////////////////////////////////////////////////////////////////////
///Server side object rsp_s_object manager
struct rsp_world_controller : rsp_controller
{
    rsp_world_controller() : rsp_controller(CT_SERVER)
    {
        _nplactive = 0;
        _active_connector = 0;

        _rnd.seed( _Timer.time() );
    }

    ///Get world version (clients must match)
    virtual ushort get_version() const = 0;

    ///External method that iterates over set of managed tiles and calls frame() on them
    virtual void iterate_tiles_frame() = 0;

    ///External method that iterates over set of managed tiles and calls diff() on them
    virtual void iterate_tiles_diff() = 0;

    ///Initialize tile system
    virtual opcd initialize_tiles() = 0;


    ///Checks if the attach request should be processed at all. Used for quick rejects due to server state or misuse
    virtual opcd allow_attach( const netAddress& addr, const uchar* rqdata, uint rqlen ) const = 0;

    ///Called to bind the controlled object to the client controller (connector)
    ///@return an error when refused 
    virtual opcd attach_client( rsp_s_connector& rsps ) = 0;

    ///Called to unbind the controlled object from the client controller (connector)
    ///@return true if the client slot should be kept for possible reconnect of the client
    virtual bool detach_client( rsp_s_connector& rsps ) = 0;
    
   
    ///Attach client connector to the world
    ///@note uses attach_client() to bind to a world object
    opcd attach_connector( netstreamudp& net, const AttachReq& atr, rsp_s_connector** con, uint* slot )
    {
        //Gactive_controller = this;

        //check if this is not a reconnect
        uint sn = _ctrls.size();
        uint ff = UMAX; //first free slot
        for( uint i=0; i<sn; ++i )
        {
            if( !_ctrls[i]._ctrl.is_set() )
            {
                if( ff == UMAX )
                    ff=i;
            }
            else if( _ctrls[i]._ctrl->is_taken() )
            {
                if( atr.ssid == _ctrls[i]._ctrl->get_ssid() )
                    return ersALREADY_EXISTS;   //client with the same ssid still connected
            }
            else if( atr.ssid == _ctrls[i]._ctrl->get_ssid() )
            {
                sn = i;
                break;
            }
        }

        if( sn >= _ctrls.size() && ff != UMAX )
            sn = ff;

        local<rsp_s_connector> lctrl;
        rsp_s_connector* ctrl;

        if( sn >= _ctrls.size() || !_ctrls[sn]._ctrl.is_set() )
            ctrl = lctrl = new rsp_s_connector;
        else
            ctrl = _ctrls[sn]._ctrl;


        uint32 ssid = _rnd.rand() ^ _Timer.time();
        if(!ssid)  ssid = 1;

        ctrl->reset();
        ctrl->init_ssid(ssid);

        opcd e;
//        SERVER_ONLY_BEGIN
        e = attach_client( *ctrl );
//        SERVER_ONLY_END

        if(!e)
        {
            ++_nplactive;
            Slot* sl;
            
            if( sn < _ctrls.size() )
                sl = &_ctrls[sn];
            else
                sl = _ctrls.add();

            if( lctrl.is_set() )
                sl->_ctrl = lctrl.eject();

            //sl->_net.set_request_compression(true);
            sl->_net.set_socket( net.get_socket(), true );
            sl->_net.set_remote_address(atr.addr);
            //sl->_net.set_packet_size( net.get_packet_size() );
            sl->_trip_time = atr.triptime();

            *slot = sn;
            *con = sl->_ctrl;
        }

        return e;
    }

    ///Detach client
    bool detach_connector( uint slot, bool force=false )
    {
        //Gactive_controller = this;
        Slot& sl = _ctrls[slot];

        //bin.set_remote_address( sl._addr );
        sl._net.reset();
        sl._net << (uint8)RSP_PACKET::DISCONNECT << BINSTREAM_FLUSH;

        LOG(0) {
            if(force) *TLOG << "FORCED ";
            *TLOG << "DETACHING \"" << sl._ctrl->get_nick() << "\" from " << *sl._net.get_address()
            << " slot: " << slot << ", ssid: " << sl._ctrl->get_ssid() << " at " << timefmt() << BINSTREAM_FLUSH;
        }

        bool ds = detach_client( *sl._ctrl );
        if( !force && ds )
        {
            //keep the slot for reconnects
            sl._ctrl->reset();
        }
        else
        {
            sl._ctrl.destroy();
            uint n = _ctrls.size();
            //discard trailing elements
            if( slot == n-1 )
            {
                for( ; n>0; )
                {
                    --n;
                    if( _ctrls[n]._ctrl.is_set() )  { ++n; break; }
                }
                _ctrls.need(n);
            }
        }

        --_nplactive;
        return ds;
    }


    void request_detach_all_connectors()
    {
        _reqdetachall = true;
    }

    ///Process client data
    int accept_client_data( netstreamudp& bin, uint slot, uint32 ssid )
    {
        if( slot >= _ctrls.size() )
            return 0;   //invalid slot

        Slot& s = _ctrls[slot];
        if( !s._ctrl.is_set() )
            return 0;   //a disconnected slot

        if( s._ctrl->get_ssid() != ssid )
            return 0;  //invalid session id

        //allow port number change
        ushort port = bin.get_port();
        if( port != s._net.get_port() )
            s._net.set_port(port);

        if( *s._net.get_address() != *bin.get_address() )
            return 0;   //a hijack attempt?

        //ok, remaining stuff is received by the connector
        return s._ctrl->patch(bin);
    }

    ///Handle client's request for disconnect
    bool accept_client_disconnect( netstreamudp& bin, uint slot, uint32 ssid )
    {
        if( slot >= _ctrls.size() )
            return false;  //invalid slot

        if( !_ctrls[slot]._ctrl.is_set() )
            return false;   //a disconnected slot

        if( _ctrls[slot]._ctrl->get_ssid() != ssid )
            return false;   //invalid session id

        return detach_connector(slot);
    }

    void process_frame( uint stateid )
    {
        _stateid = stateid;

        iterate_tiles_frame();
    }

    void process_diffs()
    {
        rsp_controller::current = this;

        if(_reqdetachall)
        {
            uint n = _ctrls.size();
            for( uint i=0; i<n; ++i ) {
                if( _ctrls[i]._ctrl.is_set() )
                    _ctrls[i]._ctrl->drop();
            }
            //_ctrls.reset();

            _nplactive = 0;
            _reqdetachall = false;
            return;
        }

        //compute diffs for all connected controllers
        // diffs are computed for the latest state id only
        uints n = _ctrls.size();
        for( uint i=0; i<n; ++i )
        {
            _active_connector = _ctrls[i]._ctrl;
            rsp_s_connector* ctrl = _active_connector;

            if(!ctrl)  continue;
            if( ctrl->get_ssid() == 0 )  continue;   //disconnected

            _ctrls[i]._limit += RSPS_SETTINGS._maxpacketperframe;
            if( _ctrls[i]._limit > 0 )
                _ctrls[i]._limit = 0;

/*
            //skip the diff step if instructed to do so
            if( _ctrls[i]._waitframes > 0 ) {
                --_ctrls[i]._waitframes;
                continue;
            }
*/
            if( _ctrls[i]._limit < 0 )
                continue;

            netstreamudp& net = _ctrls[i]._net;
/*
            if( bin.needs_send() )
            {
                uints sz = (uints)bin.get_size();

                //packets from previous diff pending
                _ctrls[i]._limit -= bin.send( RSPS_SETTINGS._maxpacketsize );

                if( !bin.needs_send() )
                {
                    uint asz = bin.get_sendsize();

                    LOG(1) {
                        *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " multipart datagram sent: "
                        << sz << " bytes (actual: " << asz << " bytes, "
                        << (asz*100/sz) << "% compression)" << BINSTREAM_FLUSH;
                    }
                }

                continue;
            }

            DASSERT( bin.get_size() == 0 );
*/

            if( !ctrl->diff(net) )
                detach_connector(i,true);
/*
            {
                uints mss = (uints)net.get_size();
                //DASSERT( mss <= 15  ||  0 != *(ushort*)bin.dbg_get_data(13) );

                //bin.set_remote_address( _ctrls[i]._addr );       //set remote address
                //_ctrls[i]._limit -= net.send( RSPS_SETTINGS._maxpacketsize );
                //net.flush();

                if( mss > 32 ) {
                    LOG(2) {
                        *TLOG << timefmt(RSP_SETTINGS.active_frame()) << " sending " << mss << " bytes to client " << i
                        << ", ackstate: " << timefmt(ctrl->get_acked_frame())
                        << " at " << timefmt() << BINSTREAM_FLUSH;
                    }
                }
            }*/
        }

        _active_connector = 0;
    }


    uint get_active_players() const                 { return _nplactive; }
    uint get_total_players() const                  { return _ctrls.size(); }

protected:
    ///Slot for the connected client
    struct Slot
    {
        local<rsp_s_connector> _ctrl;
        netstreamudp _net;
        uint _trip_time;
        int _limit;

        Slot() {
            _trip_time = 0;
            _limit = 0;
        }
    };


public:
    rsp_s_connector* _active_connector;
    rnd_strong _rnd;

protected:
    friend struct rsp_s_connector;

    dynarray<Slot> _ctrls;                  ///< controllers for each remote client

    uint _nplactive;                        ///< active (connected now) players
    bool _reqdetachall;
};


#endif //__RSP_S_ROOM__HEADER_FILE__
